#pragma once
#include <winsock2.h> //sockets
#include <iostream> //obsluga strumieni
#include "bd.h"
#include "connector/include/mysql.h"
#include <string>

#pragma hdrstop

#define LISTENING_PORT 1000 // port, na którym użytkowniocy będą się łączyć

#define MAX_CLIENTS 20 // ilośc obsługiwanych połączeń

using std::string;
// kontekst klienta
struct ClientContext
{
    HANDLE thread; 	//uchwyt do watku
    SOCKET sock; 	//socket klienta
    int id; 		//id klienta
};

static CRITICAL_SECTION cs; /* To jest obiekt sekcji krytycznej – raz zainicjalizowany nie może być przesunięty w pamięci*/
                            /* Jeżeli programujesz w językach obiektowych, zadeklaruj to jako niestatyczną składową twojej klasy */


namespace SocketServer
{
    // socket serwera
    SOCKET sock;
    // klienci - określenie maksymalnej liczby
    ClientContext clients[MAX_CLIENTS];

    using namespace std;

    string intToStr(int n)
    {
        string tmp, ret;
        if(n < 0)
        {
            ret = "-";
            n = -n;
        }
        do
        {
            tmp += n % 10 + 48;
            n -= n % 10;
        }
        while(n /= 10);
        for(int i = tmp.size()-1; i >= 0; i--) ret += tmp[i];
        return ret;
    }

    //funkcja zamykająca Server, zeruje tablice klientow
    inline void ShutdownServer()
    {
    // zamykamy otwarte socket'y klientów
        for (int i = 0; i < MAX_CLIENTS; i++) //pętla przechodzi po liscie klientow
            if (clients[i].sock != INVALID_SOCKET)
            {
                closesocket (clients[i].sock);
                clients[i].sock = INVALID_SOCKET;
            }

        closesocket (sock);
        sock = INVALID_SOCKET;
    }

    //Private
    inline DWORD WINAPI processClient (LPVOID ctx) //ten proces odpowiada za komunikację z podlaczonym klientem
    {
        /* Zainicjalizuj sekcję krytyczną przed wejściem do wielowątkowego kontekstu */
        InitializeCriticalSection(&cs);

        //INICJALIZACJA ZMIENNYCH:
        //pojedynczy klient przyjmowany jako arg powyższej funkcji
        ClientContext *context = (ClientContext*)ctx;
        //nadchodzacy napis od klienta(w wersji string)
        string inputString;
        //nadchodzacy napis od klienta(w wersji tablica char'ow)
        char inputBuffer[1000];
        int ret;

        string *dane;
        string message;
        string ciapek = "'";

        memset(inputBuffer, 0, 513);



        string tmp = "Klient o ID " + intToStr(context->id) + " podlaczony!";
        //MessageBox(NULL, tmp.c_str(), "Server", MB_ICONWARNING);
        SetWindowText(hStaticField, tmp.c_str());

        // wysyłamy komunikat inicjąjący komunikację, pierwszy po połączeniu rozpoczyna wysyłać serwer
        message = "Serwer Cie wita :-)";
        send(context->sock, message.c_str(), message.size()+1, 0);

        message = "";
        memset(inputBuffer, 0, 1000);
        inputString = "";

        /* Wejdź do sekcji krytycznej – inne wątki są zablokowane */
        EnterCriticalSection(&cs);
        while(true)
        {
            ret = recv(context->sock,inputBuffer,1000,0);

            if(ret == 0 || ret == SOCKET_ERROR )
            {
                // połączenie zamknięte po stronie klienta
                //MessageBox(NULL, "Klient zamknal polaczenie z serwerem.", "Server", MB_ICONWARNING);
                message = "Klient o ID " + intToStr(context->id) + " zamknal polaczenie z serwerem";

                SetWindowText(hStaticField, message.c_str());

                //cout << "Klient zamknął połączenie z serwerem.\n";
                closesocket (context->sock);
                context->sock = INVALID_SOCKET;
                break;
            }

            // dodajemy do wejsciowego stringa
            inputString = inputBuffer;

            // BEGIN OF : PROCEDURY KLIENT-SERWER:

            //POBIERANIE KOMPLETNYCH DANYCH PACJENTA
            if(inputString.find("getPacjent=") != string::npos)
            {
                size_t pos = inputString.find("getPacjent=") + string("getPacjent=").length();
                string pesel = inputString.substr(pos,11);
                dane = pobierzdane(pesel);
                if(dane[0] == "")
                {
                    message = "Puste dane";
                }
                else
                {
                    for(int i = 0; i < 9; ++i) message += dane[i] + ";";
                }
                send(context->sock, message.c_str(), message.size()+1, 0);
                message ="";
            }
/*
            //POBIERANIE KOMPLETNYCH DANYCH DAT
            else if(inputString.find("getDaty=") != string::npos)
            {
                size_t pos = inputString.find("getDaty=") + string("getDaty=").length();
                string pesel = inputString.substr(pos,11);
                dane = pobierzdaty(pesel);
                if(dane[0] == "")
                {
                    message = "Puste dane";
                }
                else
                {
                    for(int i = 0; i < 10; ++i) message += dane[i] + ";";
                }
                send(context->sock, message.c_str(), message.size()+1, 0);
                //message ="";
            }
*/
            //POBIERANIE KOMPLETNYCH DANYCH CHOROBY
            else if(inputString.find("getChoroba=") != string::npos)
            {
                size_t pos = inputString.find("getChoroba=") + string("getChoroba=").length();
                string pesel = inputString.substr(pos,11);
                string choroba = inputString.substr(pos + 12);
                dane = pobierzchorobe(pesel, choroba);
                if(dane[0] == "")
                {
                    message = "Puste dane";
                }
                else
                {
                    for(int i = 0; i < 5; ++i) message += dane[i] + ";";
                }
                send(context->sock, message.c_str(), message.size()+1, 0);
                //message ="";
            }

            //DODAWANIE PACJENTA
            else if(inputString.find("addPacjent=") != string::npos)
            {
                size_t pos = inputString.find("addPacjent=") + string("addPacjent=").length();
                string str = inputString.substr(pos);

                char * cstr, *p;
                bool result;
                string * params = new string[9];
                //imie, nazwisko, pesel, adres, uwagi, alergie, przewlekle, przebyte, obecnie

                cstr = new char [str.size()+1];
                strcpy (cstr, str.c_str());


                p=strtok (cstr,";");
                int i = 0;
                while (p!=NULL)
                {
                    params[i] = string(p);
                    p=strtok(NULL,";");
                    i++;
                }

                delete[] cstr;

                result = dodajpac(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7], params[8]);

                if(result)
                    message = "Dodano pacjenta";
                else
                    message = "Blad podczas dodawania pacjenta";
                send(context->sock, message.c_str(), message.size()+1, 0);
                //message ="";
            }
/*
            //DODAWANIE DAT
            else if(inputString.find("addDaty=") != string::npos)
            {
                size_t pos = inputString.find("addDaty=") + string("addDaty=").length();
                string str = inputString.substr(pos);

                char * cstr, *p;
                bool result;
                string * params = new string[13];
                //id, peselw, nowa, pierwsza, druga, trzecia, czwarta, piata, szosta, siodma, osma, dziewiata, dziesiata

                cstr = new char [str.size()+1];
                strcpy (cstr, str.c_str());


                p=strtok (cstr,";");
                int i = 0;
                while (p!=NULL)
                {
                    params[i] = string(p);
                    p=strtok(NULL,";");
                    i++;
                }

                delete[] cstr;

                result = dodajdate(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7], params[8], params[9], params[10], params[11], params[12]);

                if(result)
                    message = "Dodano daty";
                else
                    message = "Blad podczas dodawania pacjenta";
                send(context->sock, message.c_str(), message.size()+1, 0);
                //message ="";
            }
*/
            //DODAWANIE CHOROBY
            else if(inputString.find("addChoroba=") != string::npos)
            {
                size_t pos = inputString.find("addChoroba=") + string("addChoroba=").length();
                string str = inputString.substr(pos);

                char * cstr, *p;
                bool result;
                string * params = new string[7];
                // peseld, id, nazwad, poczatek, leczenie, koniec, gdzie_leczyl

                cstr = new char [str.size()+1];
                strcpy (cstr, str.c_str());



                p=strtok (cstr,";");
                int i = 0;
                while (p!=NULL)
                {
                    params[i] = string(p);
                    p=strtok(NULL,";");
                    i++;
                }

                delete[] cstr;

                result = dodajchor(params[0], params[1], params[2], params[3], params[4], params[5], params[6]);

                if(result)
                    message = "Dodano chorobe";
                else
                    message = "Blad podczas dodawania pacjenta";
                send(context->sock, message.c_str(), message.size()+1, 0);
                //message ="";
            }

            //UAKTUALNIENIE PACJENTA
            else if(inputString.find("updPacjent=") != string::npos)
            {
                size_t pos = inputString.find("updPacjent=") + string("updPacjent=").length();
                string str = inputString.substr(pos);

                char * cstr, *p;
                bool result;
                string * params = new string[10];
                // imie, nazwisko, pesels, adres, uwagi, alergie, przewlekle, przebyte, obecnie, peselw

                cstr = new char [str.size()+1];
                strcpy (cstr, str.c_str());


                p=strtok (cstr,";");
                int i = 0;
                while (p!=NULL)
                {
                    params[i] = string(p);
                    p=strtok(NULL,";");
                    i++;
                }

                delete[] cstr;

                result = updpac(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7], params[8], params[9]);

                if(result)
                    message = "Zaktualizowano pacjenta";
                else
                    message = "Blad podczas dodawania pacjenta";
                send(context->sock, message.c_str(), message.size()+1, 0);
                //message ="";
            }
/*
            //UAKTUALNIENIE DAT
            else if(inputString.find("updDaty=") != string::npos)
            {
                size_t pos = inputString.find("updDaty=") + string("updDaty=").length();
                string str = inputString.substr(pos);

                char * cstr, *p;
                bool result;
                string * params = new string[12];
                // peselw, pierwsza, druga, trzecia, czwarta, piata, szosta, siodma, osma, dziewiata, dziesiata, peself

                cstr = new char [str.size()+1];
                strcpy (cstr, str.c_str());

                // cstr now contains a c-string copy of str

                p=strtok (cstr,";");
                int i = 0;
                while (p!=NULL)
                {
                    params[i] = string(p);
                    p=strtok(NULL,";");
                    i++;
                }

                delete[] cstr;

                result = upddat(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7], params[8], params[9], params[10], params[11]);

                if(result)
                    message = "Zaktualizowano daty";
                else
                    message = "Blad podczas dodawania pacjenta";
                send(context->sock, message.c_str(), message.size()+1, 0);
                //message ="";
            }
*/
            //UAKTUALNIENIE CHOROBY
            else if(inputString.find("updChoroba=") != string::npos)
            {
                size_t pos = inputString.find("updChoroba=") + string("updChoroba=").length();
                string str = inputString.substr(pos);

                char * cstr, *p;
                bool result;
                string * params = new string[9];
                //  id, nazwa, poczatek, leczenie, koniec, peselc, gdzie_leczyl, nazwaw, peselw

                cstr = new char [str.size()+1];
                strcpy (cstr, str.c_str());

                // cstr now contains a c-string copy of str

                p=strtok (cstr,";");
                int i = 0;
                while (p!=NULL)
                {
                    params[i] = string(p);
                    p=strtok(NULL,";");
                    i++;
                }

                delete[] cstr;

                result = updcho(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7], params[8]);

                if(result)
                    message = "Zaktualizowano chorobe";
                else
                    message = "Blad podczas dodawania pacjenta";

                send(context->sock, message.c_str(), message.size()+1, 0);
                //message ="";
            }

            // END OF : PROCEDURY KLIENT-SERWER:

            //WYWO£ANIA PROCEDURY ROZLACZENIA KLIENTA
            else if (inputString == "exit")
            {
                //send(context->sock, message.c_str(), message.size()+1, 0);
                closesocket (context->sock);
                context->sock = INVALID_SOCKET;

                //message = "Klient prosi o rozlaczenie";
                //MessageBox(NULL, "Klient prosi o odlaczenie od serwera...", "Server", MB_ICONWARNING);

                tmp = "Klient o ID " + intToStr(context->id) + " wymusil rozlaczenie.";
                SetWindowText(hStaticField, tmp.c_str());

                break;
            }

            //WYWO£ANIE PROCEDURY ZABIJANIA SERWERA
            else if (inputString == "kill")
            {
                // jezeli klient chce wylaczyc serwer to bezposrednio zamykamy
                // scketa z ktorego korzysta serwer

                SocketServer::ShutdownServer();

                //MessageBox(NULL, "Klient prosi o  zamkniecie serwera...", "Server", MB_ICONWARNING);
                SetWindowText(hStaticField, "Klient wymusil zatrzymanie serwera.");
                break;
            }
            else
            {
                // jezeli klient nie wyslal zadnej z powyzszych komend wysyłamy nieznane polecenie
                message = "Nieznane polecenie: " + ciapek + inputString + ciapek;
                send(context->sock, message.c_str(), message.size()+1, 0);
            }

            // czyscimy bufory
            memset(inputBuffer, 0, 1000);
            inputString = "";
        }
        /* Opuść sekcję krytyczną – inne wątki mogą teraz EnterCriticalSection() */
        LeaveCriticalSection(&cs);
        context->thread = NULL;
        return 0;
    }

    inline DWORD WINAPI createServer(LPVOID ctx)
    {
        WSADATA         wsaData;
        SOCKADDR_IN     saddr; //adres polaczenia klienta
        SOCKET          client; //socket klienta
        int             processConnections = 5;
        int             newId;

    // zerujemy konteksty klientów
        for (int i = 0; i < MAX_CLIENTS; i ++)
        {
            clients[i].thread = NULL;
            clients[i].sock   = INVALID_SOCKET;
            clients[i].id     = -1;
        }

    // uruchamiamy serwer
        WSAStartup( MAKEWORD(2,2), &wsaData );

        sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        memset( (void*)&saddr, 0, sizeof(saddr) );
        saddr.sin_family = AF_INET;// host byte order
        saddr.sin_port = htons(LISTENING_PORT);
        saddr.sin_addr.s_addr = htonl(INADDR_ANY);// uzupełnij moim adresem IP
    //memset( &( saddr.sin_zero ), '\0', 8 );// wyzeruj resztę struktury

        if ( bind(sock, (sockaddr*)&saddr, sizeof(saddr)) == SOCKET_ERROR )
        {
            //MessageBox(NULL, "Wystapil blad podczas bindowania adresu!\n", "Server", MB_ICONWARNING);
            SetWindowText(hStaticField, "Wystapil blad podczas bindowania adresu!");

            return 1;
        }

        if (listen(sock, MAX_CLIENTS) == SOCKET_ERROR)
        {
            //MessageBox(NULL, "Ustawienie gniazda w tryb nasluchiwania nie powiodlo sie.", "Server", MB_ICONWARNING);
            SetWindowText(hStaticField, "Ustawienie gniazda w tryb nasluchiwania nie powiodlo sie.");

            closesocket (sock);
            return 2;
        }

        while (1)
        {
    // akceptujemy nowego klienta
            client = accept(sock, NULL, NULL);

            if (client == INVALID_SOCKET)
            {
    // je¿eli dostajemy WSAENOTSOCKET to znaczy, ze ktorys z klientow zamknal
    // naszego nasluchujacego socketa
                if (WSAGetLastError() == WSAECONNRESET)
                {
                    //MessageBox(NULL, "WSAECONNRESET - nasluch socketa zostal zamkniety.", "Server", MB_ICONWARNING);
                    SetWindowText(hStaticField, "WSAECONNRESET - nasluch socketa zostal zamkniety.");

                }
                else break;
            }
            else
            {
    // szukamy wolnego slota
                newId = -1;
                for (int i = 0; i < MAX_CLIENTS; i ++)
                    if (clients[i].sock == INVALID_SOCKET)
                    {
                        newId = i;
                        break;
                    }

                if (newId == -1)
                {
                    string tmp = "Serwer nie obsluguje wiekszej ilosci klientow jednoczesnie niz: " + intToStr(MAX_CLIENTS);
                    //MessageBox(NULL, tmp.c_str(), "Server", MB_ICONWARNING);
                    SetWindowText(hStaticField, tmp.c_str());
                }
                else
                {
                    // dodajemy nowego klienta i startujemy wątek
                    clients[newId].sock   = client;
                    clients[newId].id     = newId;
                    clients[newId].thread = CreateThread (NULL, 0, processClient, (LPVOID)&clients[newId], 0, NULL);

                    // jeśli utworzenie wątku nie powiodło się => zerowanie socketu (zwalnianie miejsca), zwalnianie id
                    if (clients[newId].thread == NULL)
                    {
                        clients[newId].sock = INVALID_SOCKET;
                        clients[newId].id   = -1;
                        //MessageBox(NULL, "Utworzenie watku dla klienta nie powiodlo sie.", "Server", MB_ICONWARNING);
                        SetWindowText(hStaticField, "Utworzenie watku dla klienta nie powiodlo sie.");
                    }
                }

            }
        }
        WSACleanup();
        /* Uwolnij obiekt systemowy po zakończeniu wszystkiego – zwykle na końcu kodu czyszczącego */
        DeleteCriticalSection(&cs);
    }
}
